from django.utils.deprecation import MiddlewareMixin
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
from apps.teachers.models import Teacher
import logging

logger = logging.getLogger('django')


# 自定义中间件统一返回响应数据的格式
class CustomResponseMiddleware(MiddlewareMixin):

    def process_response(self, request, response):
        # 只处理DRF的响应
        if isinstance(response, Response):
            status_code = response.status_code
            data = response.data
            logger.info(f"视图返回数据: {data}")
            # 将字符串封装成字典数据
            if isinstance(data, str):
                data = {"message": response.data}
            response_data = dict()

            # 根据状态码返回不同响应数据
            if str(status_code).startswith('2'):
                response_data["code"] = 0
                response_data["message"] = data.pop("message", "请求成功!") if isinstance(data, dict) else "请求成功!"
                # 保证数据格式为: {"data": 数据}
                if data or data == []:
                    if "data" not in data:
                        response_data["data"] = data
                    else:
                        response_data["data"] = data["data"]
            else:
                response_data["code"] = 1
                response_data["message"] = data.pop("message", "请求失败!")
                if data:
                    if "errors" not in data:
                        response_data["errors"] = data
                    else:
                        response_data["errors"] = data["errors"]

            """
            【注意】: 
            在 Django REST framework 中，当序列化器校验失败时返回的响应码不是 2xx 范围内的状态码时，
            DRF 会将响应数据转换为错误响应，并在后续处理中重新设置响应数据，覆盖掉中间件中的修改。
            这是因为 DRF 在处理异常情况时会生成一个标准的错误响应，其中包含有关错误的详细信息，这可能会覆盖中间件中对响应数据的修改。
            """
            # 创建一个 JSON 渲染器实例()
            renderer = JSONRenderer()
            # 前端axios 接收到204状态码会将接收的数据置空,不便于axios在响应拦截器中统一根据数据统一处理
            # 所以这里修改状态码为209,防止axios将数据置空
            if response.status_code == 204:
                response.status_code = 209

            response.content = renderer.render(response_data)
            response['Content-Type'] = 'application/json'

        return response


"""
分析rest_framework/views.py/APIView/handle_exception源码有如下介绍: 
"WWW-Authenticate header for 401 responses, else coerce to 403"

这句话的意思是：对于 401（未授权）响应设置 “WWW-Authenticate” 头部信息，否则强制转换为 403（禁止访问）响应
【Django REST framework 框架这么设置的原因】:
1. 灵活性考虑: 开发者可能希望在未认证的情况下执行一些额外的逻辑，比如记录日志、触发特定的事件或进行统计等。如果框架自动返回 401，可能会干扰这些自定义的处理流程
2. 权限与认证分离:  DRF将认证和权限检查分开处理。perform_authentication 主要负责认证用户身份，但权限检查可能涉及更多复杂的逻辑。仅仅因为认证未通过并不意味着一定返回 401，还需要考虑权限方面的因素.例如，一个请求可能因为权限不足而被拒绝，此时返回 403 更合适。如果框架在认证未通过时自动返回 401，可能会掩盖真正的权限问题。
3. 中间件和其他处理环节:项目中可能存在多个中间件和处理环节，它们可能会对未认证的情况进行干预。框架不能假设在所有情况下都应该自动返回 401，因为其他中间件可能有不同的处理逻辑;某些中间件可能会根据特定的条件修改响应状态码或添加额外的头部信息，以满足特定的安全需求或与其他系统的集成要求。
【总结】:
为了满足不同项目的需求，开发者需要根据具体情况手动处理 WWW-Authenticate 头部和返回适当的状态码，以确保应用的安全性和正确性。同时，这也允许开发者更好地控制应用的行为，适应复杂的业务场景
【解决方案】：
使用中间件自动鉴别401（未授权），添加WWW-Authenticate 头部，修改状态码
"""


# 在未认证情况下添加 "WWW-Authenticate" 头部以便正确返回 401
class AddWWWAuthenticateHeaderMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if response.status_code == 403 and "身份认证信息未提供" in response.data.get('detail', ''):
            response.status_code = 401
            response['WWW-Authenticate'] = 'error="invalid_token", error_description="Token is invalid or expired"'
        return response


# 刷新用户token
class RefreshUserToken(MiddlewareMixin):

    def process_response(self, request, response):
        # Django 框架会将 header 名称全部转换为大写; 其次DRF接收到的header名称都会在前面加上 HTTP_ 前缀
        token = request.META.get('HTTP_AUTHORIZATION')

        # 当前用户登录状态下刷新token
        user = Teacher.checkout_user_by_token(token)
        if user:
            new_token = user.generate_user_token()
            # 在响应头中返回最新的token
            response['Refresh-Token'] = new_token
            # 允许前端访问的响应头名称
            response['Access-Control-Expose-Headers'] = 'refresh-token'

        return response
